package com.siyoumi.exception;

import com.siyoumi.component.LogPipeLine;
import com.siyoumi.component.XApp;
import com.siyoumi.config.SysConfig;
import com.siyoumi.component.api.jj.JJ;
import com.siyoumi.component.api.jj.RobotType;
import com.siyoumi.entity.SysLog;
import com.siyoumi.service.SysLogService;
import com.siyoumi.util.XJson;
import com.siyoumi.util.XReturn;
import com.siyoumi.util.XStr;
import com.siyoumi.validator.XValidator;
import com.siyoumi.component.http.XHttpContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

//异常全局配置
@Slf4j
@RestControllerAdvice
public class BaseExceptionHandler {
    /**
     * 打印错误信息
     *
     * @param ex
     */
    static public void printStackTrace(Exception ex) {
        if (SysConfig.getBean().isDev()) {
            ex.printStackTrace();
        }
    }

    @ExceptionHandler(value = XException.class)
    public XReturn exceptionHandler(HttpServletRequest req, HttpServletResponse res, XException e) {
        XReturn r = e.getR();
        log.error(XJson.toJSONString(r));
        //
        res.setStatus(200);
        if (r.getErrCode().equals(EnumSys.SYS_VAILD.getErrcode())) {
            //验证错误
            return r;
        }
        //
        //打印错误信息
        if (SysConfig.getBean().isDev()) {
            e.printStackTrace();
        }
        //BaseExceptionHandler.printStackTrace(e);
        //
        List<StackTraceElement> listEx = listEx(e);
        if (e.getShowErrInfo()) {
            StackTraceElement firstEx = null;
            if (!listEx.isEmpty()) {
                firstEx = listEx.get(0);
            }

            String errInfo = errInfo(firstEx);
            r.setData("err_info", errInfo);
        }

        return r;
    }

    @ExceptionHandler(value = XWebException.class)
    public Object exceptionHandler(HttpServletRequest req, HttpServletResponse res, XWebException e) {
        XReturn r = e.getR();
        log.error(XJson.toJSONString(r));
        BaseExceptionHandler.printStackTrace(e); //打印错误信息
        //
        res.setStatus(200);

        List<StackTraceElement> listEx = listEx(e);
        StackTraceElement firstEx = null;
        if (!listEx.isEmpty()) {
            firstEx = listEx.get(0);
        }

        String errInfo = errInfo(firstEx);
        r.setData("err_info", errInfo);

        String html = "";
        try {
            html = XApp.getFileContent("view/sys/error.html");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        if (XStr.isNullOrEmpty(html)) {
            return XReturn.getR(50083, "系统缺少error.html");
        }
        html = html.replace("{#errmsg}", r.getErrMsg())
                .replace("{#errcode}", r.getErrCode().toString())
        ;

        return html;
    }


    /**
     * 验证异常
     *
     * @return XReturn
     */
    @ExceptionHandler(value = BindException.class)
    public XReturn exceptionHandler(HttpServletRequest req, HttpServletResponse res, BindException e) {
        XReturn r = XValidator.getResult(e, false, false);
        res.setStatus(200);
        log.error(XJson.toJSONString(r));

        return r;
    }

    /**
     * 请求方法异常
     *
     * @return XReturn
     */
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public XReturn exceptionHandler(HttpServletRequest req, HttpServletResponse res, HttpRequestMethodNotSupportedException e) {
        BaseExceptionHandler.printStackTrace(e);
        XReturn r = EnumSys.SYS.getR();

        String[] supportedMethods = e.getSupportedMethods();
        String errmsg = XStr.concat("当前请求是", e.getMethod());
        if (supportedMethods != null && supportedMethods.length > 0) {
            errmsg = XStr.concat(errmsg, "，请使用", supportedMethods[0]);
        }
        r.setErrMsg(errmsg);
        r.setData("exception", e.getClass().getName());
        r.setData("message", e.getMessage());
        r.setData("method", e.getMethod());
        r.setData("supper_method", e.getSupportedMethods());

        res.setStatus(200);
        log.error(XJson.toJSONString(r));

        return r;
    }

    /**
     * null
     *
     * @return XReturn
     */
    @ExceptionHandler(value = {NullPointerException.class})
    public XReturn exceptionHandler(HttpServletRequest req, HttpServletResponse res, NullPointerException e) {
        BaseExceptionHandler.printStackTrace(e);

        XReturn r = EnumSys.SYS.getR();
        r.setErrMsg("参数等于null");
        r.setData("exception", e.getClass().getName());

        List<StackTraceElement> listEe = listEx(e);
        List<String> errInfo = listEe.stream().map(this::errInfo).collect(Collectors.toList());
        r.setData("err_info", errInfo);
        res.setStatus(200);
        log.error(XJson.toJSONString(r));

        return r;
    }

    /**
     * 处理其他异常
     *
     * @return XReturn
     */
    @ExceptionHandler(value = Exception.class)
    public XReturn exceptionHandler(HttpServletRequest req, HttpServletResponse res, Exception e) {
        res.setHeader("Access-Control-Allow-Origin", "*");

        log.error(e.getMessage());
        //XLog.error(this.getClass(), e.getStackTrace());
        BaseExceptionHandler.printStackTrace(e); //打印错误信息

        XReturn r = EnumSys.SYS.getR();
        if (XStr.hasAnyText(e.getMessage())) {
            r.setErrMsg(e.getMessage());
        }
        r.setData("exception", e.getClass().getName());

        List<StackTraceElement> listEe = listEx(e);
        List<String> errInfo = listEe.stream().map(this::errInfo).collect(Collectors.toList());
        r.setData("err_info", errInfo);
        if (SysConfig.getIns().isDev()) {
            r.setData("stack_trace", listEe);
        }
        //r.setData("class", e.getClass());

        Boolean sendJJ = true;
//        if (e instanceof RetryableException // 请求超时
//        ) {
//            sendJJ = false;
//        }
        if (r.getErrMsg().startsWith("Failed to parse multipart servlet request")) { // 请求中断异常
            sendJJ = false;
        }
        if (r.getErrMsg().contains("Lock wait timeout exceeded")) { // 事务锁超时
            sendJJ = false;
        }
        if (r.getErrMsg().contains("Broken pipe")) {
            sendJJ = false;
        }
        if (SysConfig.getIns().isDev()) {
            sendJJ = false;
        }

        if (sendJJ) {
            //发钉钉
            //获取第一个带loader_name的错误信息
            if (listEe.size() > 0) {
                StackTraceElement firstEx = listEe.get(0);
                sendJJ(r, firstEx);
            }
        }

        res.setStatus(500);
        return r;
    }


    //发钉钉
    private void sendJJ(XReturn r, StackTraceElement firstEx) {
        StringBuilder msg = new StringBuilder();
        String errmsg = r.getErrMsg();

        String errInfoFirst = errInfo(firstEx);
        msg.append(XStr.concat("**", errmsg.replaceAll("#", ""), "**\n\n", errInfoFirst, "\n\n"));

        // 记录log日志
        if (XStr.hasAnyText(XHttpContext.getX(false))) {
            List<String> errInfo = r.getData("err_info");
            String errInfoStr = errInfo.stream().collect(Collectors.joining("\n"));
            SysLog entityLog = SysLogService.getBean().addSysErrorLog(errmsg, errInfoStr);

            String logId = entityLog.getKey();
            String appRoot = SysConfig.getIns().getAppRoot();
            String errorUrl = XStr.concat(appRoot, "app/sys_log/error/", logId);
            msg.append(XStr.concat("[-- 查看详情 --](", errorUrl, ") \n\n"));
        }

        log.debug("发钉钉-BEGIN");
        JJ jj = JJ.getIns(RobotType.ERROR);
        jj.send(msg.toString(), XHttpContext.getUrlFull(), false);
        log.debug("发钉钉-END");
    }


    /**
     * 收集异常
     *
     * @param e
     */
    public List<StackTraceElement> listEx(Throwable e) {
        List<StackTraceElement> listEx = new ArrayList<>();
        Boolean beginGet = false;
        for (StackTraceElement exEle : e.getStackTrace()) {
            if (listEx.size() >= 10) break;
            if (!beginGet && XStr.startsWith(exEle.getClassName(), "com.siyoumi.")) {
                //从自己开发的class 开始拿
                beginGet = true;
            }
            if (beginGet) {
                listEx.add(exEle);
            }
        }

        for (StackTraceElement ex : listEx) {
            LogPipeLine.ofSys().setLogMsg(errInfo(ex));
        }
        return listEx;
    }

    /**
     * 格式化显示异常显示
     *
     * @param ex
     */
    public String errInfo(StackTraceElement ex) {
        if (ex == null) {
            return "";
        }
        String method = "";
        if (XStr.hasAnyText(ex.getMethodName())) {
            method = "." + ex.getMethodName();
        }

        String err = XStr.concat(ex.getClassName() //类路径
                , method //方法名，有可能为null
                , "(", ex.getFileName());
        if (ex.getLineNumber() > 0) {
            err += ":" + ex.getLineNumber();
        }
        err += ")";

        return err;
    }

    /**
     * 收集错误信息
     *
     * @param e
     */
    static public List<String> listErrInfo(Throwable e) {
        BaseExceptionHandler base = new BaseExceptionHandler();

        List<StackTraceElement> listEx = base.listEx(e);
        List<String> list = new ArrayList<>();
        for (StackTraceElement ex : listEx) {
            list.add(base.errInfo(ex));
        }
        return list;
    }
}

